package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itzhanshen.sys.feign.SysUserFeign;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.excel.CustomHandler;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendancePunchService;
import com.nineclock.attendance.service.AttendanceService;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NCException;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.CurrentUserHolder;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.common.utils.DistanceUtil;
import com.nineclock.dto.SysCompanyUserDTO;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class AttendancePunchServiceImpl implements AttendancePunchService {
    /**
     * 移动端打卡
     *
     * @param attendPunchDTO
     */
    @Autowired
    SysUserFeign sysUserFeign;
    @Autowired
    AttendPunchMapper attendPunchMapper;
    @Autowired
    AttendanceService attendanceService;
// 获取每个月考勤情况
    @Override
    public String generateExcelReportByCompanyId(Long cId) {
        // 获取到当月第一天,以及最后一天
        String firstDay = DateTimeUtil.getCurrentMonthFirstDay();
        String lastDay = DateTimeUtil.getCurrentMonthLastDay();
        List<Date> dates = DateTimeUtil.getDatesBetweenTwoDate(firstDay, lastDay, DateTimeUtil.TIME_FORMAT_2);
        List<String> dateList = dates.stream().map(date -> {
            return DateTimeUtil.dateToStr(date, DateTimeUtil.TIME_FORMAT_2);
        }).collect(Collectors.toList());
        // 获取当前企业所有员工
        List<SysCompanyUserDTO> companyUserDTOS = sysUserFeign.findCompanyUserByUserIdAndCompanyId(null, cId).getData();
        // 获取每个员工每天打卡情况
        List<AttendPunchDTO> list = this.everyDaysPunchStatus(firstDay,
                lastDay, companyUserDTOS, dateList);
        // 生成excel文件
        String foldname = "D://考勤数据//" + cId;
        if (!new File(foldname).exists()) {
            // 文件夹不存在,创建一个文件夹
            new File(foldname).mkdir();
        }
        String filename = foldname + "//考勤报表-" + firstDay.substring(0, 7) + ".xls";// 切割当前月前6位 2020-04-01 2020-04
        EasyExcel.write(new File(filename))
                .excelType(ExcelTypeEnum.XLS)
                .head(buildTableHeard(list))
                .registerWriteHandler(horizontalCellStyleStrategy())
                .registerWriteHandler(new CustomHandler())
                .sheet("考勤列表")
                .doWrite(data(list));
        return filename;
    }

    /**
     * 导出指定时间段考勤打卡数据
     *
     * @param startTime
     * @param endTime
     */
    @Override
    public void export(String startTime, String endTime) {
        if (startTime == null || endTime == null) {
            throw new NCException(ResponseEnum.MESSAGE_PARAMS_LOST);
        }
        // 获取当前时间段打卡数据
        List<AttendPunchDTO> members = this.getMembers(startTime, endTime);
        // 设置response下载头
        try {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = requestAttributes.getResponse();
            response.setHeader("Content-Disposition", "attachment;filename=attendance.xls");
            response.setContentType("application/octet-stream;charset=UTF-8");
            // 写入Excel
            EasyExcel.write(response.getOutputStream())
                    .excelType(ExcelTypeEnum.XLS)
                    .head(buildTableHeard(members)) // 表头
                    .registerWriteHandler(horizontalCellStyleStrategy())
                    .registerWriteHandler(new CustomHandler())
                    .sheet("考勤列表")
                    .doWrite(data(members)); //数据
        } catch (IOException e) {
            System.out.println("导出数据出现异常");
            e.printStackTrace();
        }
    }

    //设置样式
    public HorizontalCellStyleStrategy horizontalCellStyleStrategy() {
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //内容策略 - 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("阿里巴巴普惠体");
        font.setFontHeightInPoints((short) 11);
        contentWriteCellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(contentWriteCellStyle, contentWriteCellStyle);
        return horizontalCellStyleStrategy;
    }

    // 导出数据
    private List<List<String>> data(List<AttendPunchDTO> members) {
        List<List<String>> lists = new ArrayList<>();
        // 组装数据
        if (members != null) {
            for (AttendPunchDTO member : members) {
                List<String> list = new ArrayList<>();
                list.add(member.getUserName());
                list.add(member.getDepartmentName());
                list.add(member.getWorkNumber());
                list.add(member.getPost());
                List<AttendsDTO> attendsList = member.getAttendsList();
                if (attendsList != null) {
                    for (AttendsDTO attendsDTO : attendsList) {
                        list.add(attendsDTO.getAttendStatus());
                    }
                }
                lists.add(list);
            }
        }
        return lists;
    }

    //组装表头信息
    private List<List<String>> buildTableHeard(List<AttendPunchDTO> members) {
        List<List<String>> heardList = new ArrayList<>();
        heardList.add(Arrays.asList("姓名"));
        heardList.add(Arrays.asList("工号"));
        heardList.add(Arrays.asList("部门"));
        heardList.add(Arrays.asList("职位"));
        // 动态获取时间列表表头
        AttendPunchDTO attendPunchDTO = members.get(0);
        if (attendPunchDTO != null) {
            // 考勤数据列表
            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            if (CollectionUtil.isNotEmpty(attendsList)) {
                // 拿出考勤数据日期
                attendsList.stream().forEach(attendsDTO -> {
                    heardList.add(Arrays.asList(attendsDTO.getAttendDate()));
                });
            }
        }
        return heardList;
    }

    /**
     * 获取打卡数据
     *
     * @return
     */
    @Override
    public AttendPunchUserWholeDayDTO getPunchData() {
        // 获取查询打卡数据参数,获取当前时间
        Long companyId = CurrentUserHolder.get().getCompanyId();
        Long companyUserId = CurrentUserHolder.get().getCompanyUserId();
        Date date = new Date();
        String nowDate = DateTimeUtil.dateToStr(date, DateTimeUtil.TIME_FORMAT_2);

        // 查询上午打卡数据
        LambdaQueryWrapper<AttendPunch> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AttendPunch::getCompanyId, companyId)
                .eq(AttendPunch::getCompanyUserId, companyUserId)
                .eq(AttendPunch::getEffectiveValid, true)
                .eq(AttendPunch::getPunchDateStr, nowDate)
                .eq(AttendPunch::getPunchOnOffWork, AttendEnums.NOON_TYPE_MORNING.value());
        AttendPunch attendPunch = attendPunchMapper.selectOne(queryWrapper);
        AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(attendPunch, AttendPunchDTO.class);
        // 查询下午打卡数据
        LambdaQueryWrapper<AttendPunch> queryWrapper2 = new LambdaQueryWrapper<>();
        queryWrapper2.eq(AttendPunch::getCompanyId, companyId)
                .eq(AttendPunch::getCompanyUserId, companyUserId)
                .eq(AttendPunch::getEffectiveValid, true)
                .eq(AttendPunch::getPunchDateStr, nowDate)
                .eq(AttendPunch::getPunchOnOffWork, AttendEnums.NOON_TYPE_AFTERNOON.value());
        AttendPunch attendPunch2 = attendPunchMapper.selectOne(queryWrapper);
        AttendPunchDTO attendPunchDTO2 = BeanHelper.copyProperties(attendPunch2, AttendPunchDTO.class);
        // 封装数据返回
        return new AttendPunchUserWholeDayDTO(attendPunchDTO, attendPunchDTO2);
    }

    /**
     * 查询考勤列表数据
     *
     * @param startTime
     * @param endTime
     * @return
     */
    @Override
    public List<AttendPunchDTO> getMembers(String startTime, String endTime) {
        // 获取企业所有员工
        List<SysCompanyUserDTO> companyUserDTOS = sysUserFeign.queryAllCompanyUsers().getData();
        // 获取当前时间日期的集合
        List<Date> dates = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, DateTimeUtil.TIME_FORMAT_2);
        List<String> dateList = dates.stream().map(date -> {
            return DateTimeUtil.dateToStr(date, DateTimeUtil.TIME_FORMAT_2);
        }).collect(Collectors.toList());
        // 获取每个员工每天打卡情况
        List<AttendPunchDTO> list = this.everyDaysPunchStatus(startTime, endTime, companyUserDTOS, dateList);
        return list;
    }

    // 每个员工每日每个时间段打卡情况
    private List<AttendPunchDTO> everyDaysPunchStatus(String startTime, String endTime, List<SysCompanyUserDTO> companyUserDTOS, List<String> dateList) {
        List<AttendPunchDTO> list = companyUserDTOS.stream().map(sysCompanyUserDTO -> {
            // 查询员工在指定段中打卡的数据
            LambdaQueryWrapper<AttendPunch> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(AttendPunch::getCompanyId, sysCompanyUserDTO.getCompanyId());
            queryWrapper.eq(AttendPunch::getCompanyUserId, sysCompanyUserDTO.getId());
            queryWrapper.eq(AttendPunch::getEffectiveValid, true);
            queryWrapper.between(AttendPunch::getPunchDateStr, startTime, endTime);
            List<AttendPunch> attendPunches = attendPunchMapper.selectList(queryWrapper);
            // 组装基本信息
            AttendPunchDTO attendPunchDTO = new AttendPunchDTO();
            attendPunchDTO.setUserName(sysCompanyUserDTO.getUserName());
            attendPunchDTO.setWorkNumber(sysCompanyUserDTO.getWorkNumber());
            attendPunchDTO.setDepartmentName(sysCompanyUserDTO.getDepartmentName());
            attendPunchDTO.setPost(sysCompanyUserDTO.getPost());
            // 封装每日日期的考勤数据信息
            List<AttendsDTO> attendsDTOS = this.handleAttendPunchEveryDay(dateList, attendPunches);
            attendPunchDTO.setAttendsList(attendsDTOS);
            return attendPunchDTO;
        }).collect(Collectors.toList());
        return list;
    }

    /**
     * 移动端打卡
     *
     * @param attendPunchDTO
     */
    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        // 参数校验
        if (attendPunchDTO == null) {
            throw new NCException(ResponseEnum.MESSAGE_PARAMS_LOST);
        }
        // 组装考勤打卡的基本数据
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);
        Date date = new Date();
        attendPunch.setCreateTime(date);
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());
        attendPunch.setPunchTime(date);
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setPunchDateStr(DateTimeUtil.dateToStr(date, DateTimeUtil.TIME_FORMAT_2));
        // 判断是上午打卡还是下午打卡
        int type = DateTimeUtil.noonType(date);
        attendPunch.setPunchOnOffWork(type + 1);
        // 获取当前用户考勤组,如果没有,则直接返回
        AttendGroupDTO attendGroupDTO = attendanceService.queryAttendGroup();
        if (attendGroupDTO == null) {
            throw new NCException(ResponseEnum.USER_NOT_MATCH_ATTENDGROUP);
        }
        // 校验数据的有效性
        // 判断打卡的范围
        Boolean punchResult = this.validatePunchArea(attendPunchDTO, attendGroupDTO);
        if (!punchResult) {
            throw new NCException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        //attendPunch.setAreaValid(punchResult);
        attendPunch.setAreaValid(true);
        // 判断是否为工作日打卡
        boolean workPunchResult = this.validateWorkDay(attendGroupDTO);
        if (!workPunchResult) {
            // 不是工作日
            throw new NCException(ResponseEnum.PUNCH_INVALID_DAY);
        }
        // 为true为工作日,否则休息日
        attendPunch.setDayType(1);
        //attendPunch.setDayType(workPunchResult ? AttendEnums.DAY_TYPE_WORKDAY.value() : AttendEnums.DAY_TYPE_REST.value());
        // 判断是否有效打卡 上午第一次有效,下午最后一次有效
        Boolean isPunchValid = this.isValidatePunch(attendPunch, attendGroupDTO);
        if (isPunchValid) {
            // 标记打卡状态
            this.setPunchType(attendPunch, attendGroupDTO);
        }
        // 保存打卡数据信息
        attendPunchMapper.insert(attendPunch);
    }

    // 每日打卡情况信息
    private List<AttendsDTO> handleAttendPunchEveryDay(List<String> dateList, List<AttendPunch> attendPunches) {
        // 按照时间范围查询出来的考勤数据对每日日期进行分组
        // 键 -->> 每日日期,值-->> 每日上下午打卡的数据
        Map<String, List<AttendPunch>> collect = attendPunches.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));
        // 每一天打卡情况
        List<AttendsDTO> attendsDTOS = dateList.stream().map(date -> {
            AttendsDTO attendsDTO = new AttendsDTO();
            attendsDTO.setAttendDate(date);
            // 获取每一天的打卡数据
            List<AttendPunch> punches = collect.get(date);
            if (CollectionUtil.isEmpty(punches)) {
                // 没打过卡
                attendsDTO.setAttendStatus("缺卡/缺卡");
            } else {
                attendsDTO.setAttendStatus(this.handleAttendStatus(punches));
            }
            return attendsDTO;
        }).collect(Collectors.toList());
        return attendsDTOS;
    }

    // 打卡状态
    private String handleAttendStatus(List<AttendPunch> punches) {
        String amAttendStatus = "缺卡";//上午 - 考勤
        String pmAttendStatus = "缺卡";//下午 - 考勤

        for (AttendPunch punch : punches) {
            if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()) {
                amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
            } else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()) {
                pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
            }
        }
        return amAttendStatus + "/" + pmAttendStatus;
    }

    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType) {
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;
    }

    // 标记本次打卡的状态为什么 -- 正常--迟到--矿工--早退
    private void setPunchType(AttendPunch attendPunch, AttendGroupDTO attendGroupDto) {

        //获取考勤组中的 --- 允许迟到分钟数 10
        Integer allowLateMinutes = attendGroupDto.getAllowLateMinutes();
        //获取考勤组中的 ---- 旷工迟到分钟数 60
        Integer lateMinutes = attendGroupDto.getLateMinutes();

        //获取 实际打卡时间 字符串
        Date punchTime = attendPunch.getPunchTime();
        String punchTimeStr = DateTimeUtil.dateToStr(punchTime, DateTimeUtil.TIME_FORMAT_1);

        //默认正常
        AttendEnums punchType = AttendEnums.PUNCH_TYPE_OK;

        //判定
        //上午
        if (attendPunch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()) {
            //获取关联的考勤组的设置上班时间
            String startWorkTime = attendGroupDto.getStartWorkTime();

            //判定上班打卡时间
            if (DateTimeUtil.compareTime(punchTimeStr, startWorkTime, allowLateMinutes) == 1) { //返回1 , 说明 : punchTimeStr > startWorkTime + allowLateMinutes
                punchType = AttendEnums.PUNCH_TYPE_LATE;

                if (DateTimeUtil.compareTime(punchTimeStr, startWorkTime, lateMinutes) == 1) {  //返回1 , 说明 : punchTimeStr > startWorkTime + lateMinutes
                    punchType = AttendEnums.PUNCH_TYPE_STAYALAY;
                }
            }

            attendPunch.setMorningPunchType(punchType.value());
            //下午
        } else {
            String offWorkTime = attendGroupDto.getOffWorkTime();

            //判定下班打卡时间
            if (DateTimeUtil.compareTime(offWorkTime, punchTimeStr, allowLateMinutes) == 1) { //返回1 , 说明 : offWorkTime > punchTimeStr + allowLateMinutes
                punchType = AttendEnums.PUNCH_TYPE_EARLY;
                if (DateTimeUtil.compareTime(punchTimeStr, offWorkTime, lateMinutes) == 1) {  //返回1 , 说明 : punchTimeStr > startWorkTime + lateMinutes
                    punchType = AttendEnums.PUNCH_TYPE_STAYALAY;
                }
            }
            attendPunch.setAfternoonPunchType(punchType.value());
        }
    }

    // 判断是否有效打卡
    private Boolean isValidatePunch(AttendPunch attendPunch, AttendGroupDTO attendGroupDTO) {
        LambdaQueryWrapper<AttendPunch> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AttendPunch::getCompanyUserId, attendPunch.getCompanyUserId()) //打卡用户ID
                .eq(AttendPunch::getPunchDateStr, attendPunch.getPunchDateStr()) //打卡时间
                .eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork()) //上午 , 下午
                //.eq(AttendPunch::getAreaValid, true) //打卡范围有效
                .eq(AttendPunch::getDayType, 1); //工作日打卡
        Integer count = attendPunchMapper.selectCount(queryWrapper);
        if (count > 0) {
            // 打过卡
            // 判断是上午打卡还是下午打得卡
            if (attendPunch.getPunchOnOffWork() == 1) {

                // 上午打过卡,直接置于无效
                attendPunch.setEffectiveValid(false);
            } else {
                // 下午,最后一次有效
                AttendPunch attendPunch1 = new AttendPunch();
                attendPunch1.setEffectiveValid(false);
                // 更新之前的打卡记录为无效
                attendPunchMapper.update(attendPunch1, queryWrapper);
                attendPunch.setEffectiveValid(true);
            }
        } else {
            // 没有打过
            attendPunch.setEffectiveValid(true);
        }
        return attendPunch.getEffectiveValid();
    }

    // 校验工作日
    private boolean validateWorkDay(AttendGroupDTO attendGroupDTO) {
        int week = DateUtil.thisDayOfWeek();   //1: 表示周日, 2: 表示周一, 3:表示周二
        int index = 0;
        List<String> workdays = attendGroupDTO.getWorkdays(); // [1,1,1,1,1,0,0]

        if (week == 1) {
            index = 6;
        } else {
            index = week - 2;
        }

        boolean flag = Integer.valueOf(workdays.get(index)) == 1;
        return flag;
    }

    // 判断打卡的范围的有效性
    private Boolean validatePunchArea(AttendPunchDTO attendPunchDTO, AttendGroupDTO attendGroupDTO) {

        //比较"打卡坐标" 和 "考勤组打卡坐标"的距离
        Integer meter = DistanceUtil.getDistanceMeter(attendPunchDTO.getLat().doubleValue(), attendPunchDTO.getLng().doubleValue(), attendGroupDTO.getLat().doubleValue(), attendGroupDTO.getLng().doubleValue());
        //判定是否小于等于考勤组中设置的范围
        return meter <= attendGroupDTO.getAddressRange();
    }
}
